package com.timsmeet.timeperiod;

import java.util.Calendar;

import com.timsmeet.utils.ArgumentValidator;

public class TimeRangeImpl implements TimeRange {
	
	private Calendar start;
	private Calendar end;

	public TimeRangeImpl(Calendar start, Calendar end) {
		ArgumentValidator.isNotNull(start, "start");
		ArgumentValidator.isNotNull(end, "end");

		if(start.compareTo(end) <= 0) {
			this.start = TimePeriodUtils.trimCalendar(start);
			this.end = TimePeriodUtils.trimCalendar(end);
		} else {
			this.end = TimePeriodUtils.trimCalendar(start);
			this.start = TimePeriodUtils.trimCalendar(end);
		}
	}
	
	public TimeRangeImpl(Calendar start, TimeInterval interval) {
		ArgumentValidator.isNotNull(start, "start");
		ArgumentValidator.isNotNull(interval, "interval");
		
		this.start = TimePeriodUtils.trimCalendar(start);
		this.end = TimePeriodUtils.trimCalendar(start);
		if(interval.getMinutes() > Integer.MAX_VALUE) {
			throw new ArithmeticException("Interval to large to create TimeRange");
		}
		this.end.add(Calendar.MINUTE, (int)interval.getMinutes());
	}
	
	public TimeRangeImpl(TimeInterval interval, Calendar end) {
		ArgumentValidator.isNotNull(end, "end");
		ArgumentValidator.isNotNull(interval, "interval");
		
		this.end = TimePeriodUtils.trimCalendar(end);
		this.start = TimePeriodUtils.trimCalendar(end);
		if(interval.getMinutes() > Integer.MAX_VALUE) {
			throw new ArithmeticException("Interval to large to create TimeRange");
		}
		this.start.add(Calendar.MINUTE, - (int)interval.getMinutes());
	}

	@Override
	public Calendar getStart() {
		return start;
	}

	@Override
	public void setStart(Calendar start) {
		ArgumentValidator.isNotNull(start, "start");
		
		if(this.end != null 
				&& TimePeriodUtils.compareTrimedCalendar(start, this.end) > 0) {
			throw new IllegalArgumentException("TimeRange start can't be later than end.");
		}
		this.start = TimePeriodUtils.trimCalendar(start);
	}

	@Override
	public Calendar getEnd() {
		return end;
	}

	@Override
	public void setEnd(Calendar end) {
		ArgumentValidator.isNotNull(end, "end");

		if(this.start != null
				&& TimePeriodUtils.compareTrimedCalendar(end, this.start) < 0) {
			throw new IllegalArgumentException("TimeRange end can't be earlier than start.");
		}
		this.end = TimePeriodUtils.trimCalendar(end);
	}

	@Override
	public boolean isMoment() {
		if(start != null && end != null) {
			return (TimePeriodUtils.compareTrimedCalendar(start, end) == 0);
		}
		return false;
	}

	@Override
	public TimeInterval getDuration() {
		return new TimeInterval((end.getTimeInMillis() - start.getTimeInMillis()) / (TimePeriod.MILISECONDS_IN_MINUTE));
	}
	
	
	@Override
	public PeriodRelation getRelation(TimePeriod period) {
		ArgumentValidator.isNotNull(period, "period");
		return TimePeriodCalc.getRelation(this, period);
	}

	@Override
	public boolean hasInside(TimePeriod period) {
		ArgumentValidator.isNotNull(period, "period");
		return TimePeriodCalc.hasInside(this, period);
	}

	@Override
	public boolean intersectsWith(TimePeriod period) {
		ArgumentValidator.isNotNull(period, "period");
		return TimePeriodCalc.intersectsWith(this, period);
	}

	@Override
	public boolean overlapsWith(TimePeriod period) {
		ArgumentValidator.isNotNull(period, "period");
		return TimePeriodCalc.overlapsWith(this, period);
	}

	@Override
	public boolean isSamePeriod(TimePeriod period) {
		ArgumentValidator.isNotNull(period, "period");
		return TimePeriodCalc.getRelation(this, period).equals(PeriodRelation.EXACT_MATCH);
	}

	@Override
	public void expandStartTo(Calendar moment) {
		ArgumentValidator.isNotNull(moment, "moment");
		if(start.after(moment)) {
			this.start = TimePeriodUtils.trimCalendar(moment);
		}
	}

	@Override
	public void expandEndTo(Calendar moment) {
		ArgumentValidator.isNotNull(moment, "moment");
		if(end.before(moment)) {
			this.end = TimePeriodUtils.trimCalendar(moment);
		}

	}

	@Override
	public void expandTo(Calendar moment) {
		expandStartTo(moment);
		expandEndTo(moment);
	}

	@Override
	public void expandTo(TimePeriod period) {
		ArgumentValidator.isNotNull(period, "period");
		expandStartTo(period.getStart());
		expandEndTo(period.getEnd());
	}

	@Override
	public void shrinkStart(Calendar moment) {
		ArgumentValidator.isNotNull(moment, "moment");
		if(TimePeriodCalc.hasInside(this, moment)
				&& start.before(moment)) {
			this.start = TimePeriodUtils.trimCalendar(moment);
		}
	}

	@Override
	public void shrinkEnd(Calendar moment) {
		ArgumentValidator.isNotNull(moment, "moment");
		if(TimePeriodCalc.hasInside(this, moment)
				&& end.after(moment)) {
			this.end = TimePeriodUtils.trimCalendar(moment);
		}

	}

	@Override
	public void shrinkTo(TimePeriod period) {
		ArgumentValidator.isNotNull(period, "period");
		shrinkStart(period.getStart());
		shrinkEnd(period.getEnd());
	}

	@Override
	public TimeRange getIntersection(TimePeriod period) {
		ArgumentValidator.isNotNull(period, "period");
		
		if(!intersectsWith(period)) {
			return null;
		}
		Calendar resultStart = period.getStart().after(start) ? period.getStart() : start;
		Calendar resultEnd = period.getEnd().before(end) ? period.getEnd() : end;
		return new TimeRangeImpl(resultStart, resultEnd);
	}

	@Override
	public String toString() {
		return "TimeRangeImpl [start=[" + getFormatCalendar(start) + "], end=[" + getFormatCalendar(end) + "]]";
	}
	
	
	private String getFormatCalendar(Calendar calendar) {
		return
			"Year: " + calendar.get(Calendar.YEAR) +
			", Month: " + calendar.get(Calendar.MONTH) +
			", Day: " + calendar.get(Calendar.DAY_OF_MONTH) +
			", Hour: " + calendar.get(Calendar.HOUR_OF_DAY) + 
			", Minutes: " + calendar.get(Calendar.MINUTE);
	}

	

}
